package com.partsoft.dits.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.xml.sax.SAXException;

import com.partsoft.dits.message.DataAction;
import com.partsoft.dits.message.DataProperty;
import com.partsoft.dits.message.MessageConcatenationResolver;
import com.partsoft.dits.message.MessageEntity;
import com.partsoft.dits.message.MessageProcessor;
import com.partsoft.utils.Assert;

public class ExceptionUtils {
	
	private static final Map<String, String> exceptionClassCauseMethods = new HashMap<String, String>(5);
	
	static {
		exceptionClassCauseMethods.put("javax.servlet.ServletException", "getRootCause");
	}
	
	/**
	 * 把异常输出至StringWriter中
	 * @param e 异常
	 */
	public static StringWriter stackTraceToStringWriter(Throwable e) {
		Assert.notNull(e);
		StringWriter swr = new StringWriter();
		e.printStackTrace(new PrintWriter(swr));
		return swr;
	}
	
	/**
	 * 根据类类型获得上级中的具体异常
	 * @param ex 底层异常
	 * @param cause 异常类型
	 * @return 如果找不到则返回NULL
	 */
    @SuppressWarnings("unchecked")
	public static final <T> T findCause(Throwable ex, Class<T> cause)
    {
        for(; ex != null; ex = getCause(ex))
            if(ClassUtils.isAssignableValue(cause, ex))
                return (T) ex;
        return null;
    }

	
	/**
	 * 获得异常的上级异常
	 * @param ex 原始异常
	 * @return 如果有则返回上级异常，否则返回null
	 */
    public static final Throwable getCause(Throwable ex)
    {
        Throwable t = null;
        t = ex.getCause();
        if(t == null) { 
	        if(ex instanceof RemoteException)
	            return ((RemoteException)ex).detail;
	        if(ex instanceof SAXException)
	            return ((SAXException)ex).getException();
	        for (Map.Entry<String, String> exci : exceptionClassCauseMethods.entrySet()) {
	        	Class<?> clazz = null;
		        try {
		        	clazz =  ClassUtils.forName(exci.getKey(),  ExceptionUtils.class.getClassLoader());
		        } catch (Throwable e) {
		        	continue;
		        }
	        	if (ClassUtils.isAssignableValue(clazz, ex)) {
	        		Method method = ReflectionUtils.findMethod(clazz, exci.getValue(), new Class<?>[0]);
	        		if (method != null) {
	        			t = (Throwable) ReflectionUtils.invokeMethod(method, null);
	        			break;
	        		}
	        	}
	        }
        }
        return t;
    }
    
    /**
     * 获取顶层异常
     * @param ex 原始异常 
     * @return 顶层异常
     */
    public static final Throwable getRealCause(Throwable ex)
    {
        do{
            Throwable cause = getCause(ex);
            if(cause == null)
                return ex;
            ex = cause;
        } while(true);
    }
    
    public static final MessageEntity toMessageEntity(MessageProcessor mpr, Throwable e, boolean statck, MessageConcatenationResolver resolver) {
    	Assert.notNull(mpr);
    	Assert.notNull(e);
    	Map<DataProperty, String> ee = new HashMap<DataProperty, String>(2);
    	ee.put(new DataProperty("MESSAGE"), e.getMessage());
    	if (statck) {
    		ee.put(new DataProperty("STACK"), stackTraceToStringWriter(e).toString());
    	}
    	ee.put(new DataProperty("CLASS"), e.getClass().getName());
    	List<Map<DataProperty, String>> rows = new ArrayList<Map<DataProperty, String>>(1);
    	rows.add(ee);
    	return mpr.newMessage(null, Exception.class.getSimpleName().toUpperCase(), DataAction.GET, rows, resolver);
    }
    
    public static final MessageEntity toMessageEntity(MessageProcessor mpr, Throwable e, MessageConcatenationResolver resolver) {
    	return toMessageEntity(mpr, e, false, resolver);
    }
    
    public static final MessageEntity toMessageEntity(MessageProcessor mpr, Throwable e) {
    	return toMessageEntity(mpr, e, false, null);
    }
    
    public static final MessageEntity toMessageEntity(MessageProcessor mpr, Throwable e, boolean stack) {
    	return toMessageEntity(mpr, e, stack, null);
    }
    
}
